package Verifier.ArithmeticSystem;

import java.nio.*;
import java.math.BigInteger;
import Verifier.ByteTree.*;

/**
 * Service class containing functions for representing objects as ByteTrees and Vice-Versa.
 * 
 * @author Lidor Vered
 *
 */
public class ArithmeticConvertor{
	/**
	 * Converts an integer to a byte tree.
	 * 
	 * @param i the number to convert.
	 * @return the corresponding byte tree to the number i
	 * @throws Exception when the number isn't an integer or short
	 */
	public static ByteTree ToByteTree(int i) throws Exception{
		/* first we need to check the smallest representation for i */
		byte[] byteArr = null;
		char c;
		c = shortOrInt(i);
		if (c == 'i') //int is the smallest representation
			byteArr = fromIntToByteArray(i);
		else if (c == 's')
			byteArr = fromShortToByteArray((short)i);
		else // this shouldn't happened, but maybe it can be
			throw new Exception("the character " + c + " isn't one of the types.");
		// wrap the byte array with a leaf
		Leaf leaf = new Leaf(byteArr, null);
		// wrap a byte tree around the leaf
		return new ByteTree(leaf.toByteString());
	}
	
	/**
	 * Converts a byte tree to an integer. The number should be represented in the smallest type possible.
	 * 
	 * @param b the byte tree to read the int from.
	 * @return the corresponding integer to the byte tree b.
	 * @throws Exception invalid byte tree structure. also when wrong number of bytes to represent an int.
	 */
	public static int ToInt(ByteTree b) throws Exception{
		ByteTreeElement element = b.getFirstByteTreeElement();
		if (element == null || !element.isLeaf()) //empty ByteTree or not a leaf
			throw new Exception("the ByteTree " + b + " doesn't match the int representation.");
	
		// check the number of bytes representing the number
		Leaf leaf = (Leaf)element;
		byte[] data = leaf.getDataByteArray();
		int result = 0;
		
		switch (data.length) {
		case 2:
			// convert the bytes into a short
			short shortRes = (short)(((0xff & data[0]) << 8) | 
									 ((0xff & data[1]) << 0));
			result = shortRes;
			break;
		case 4:
			// convert the bytes into an int
			int intRes = (((0xff & data[0]) << 24) |
						  ((0xff & data[1]) << 16) |
						  ((0xff & data[2]) << 8)  |
						  ((0xff & data[3]) << 0));
			result = intRes;
			break;
		default:
			// this is not an int
			throw new Exception("number of bytes of data =" + data.length + " and it doesn't match number of bytes of int.");
		}
		
		return (result);
	}
	
	/**
	 * Converts an array of booleans to a byte tree.
	 * 
	 * @param b the boolean array to convert.
	 * @return the corresponding byte tree to the boolean array b.
	 * @throws Exception invalid boolean type.
	 */
	public static ByteTree ToByteTree(boolean[] b) throws Exception{
		// create a byte array to represent the boolean array
		byte[] byteArr = new byte[b.length];
		for (int i = 0; i < b.length; i++){
			// for each boolean, convert it to a byte - 0 - if false and 1 - if true
			if (b[i] == true)
				byteArr[i] = 01;
			else
				byteArr[i] = 00;
		}
		// add all the bytes to a leaf
		Leaf leaf = new Leaf(byteArr, null);
		return new ByteTree(leaf);
	}
	
	/**
	 * Converts a byte tree to an array of boolean values.
	 * 
	 * @param b the byte tree to read the boolean array from.
	 * @return the corresponding boolean array to the byte tree b.
	 * @throws Exception invalid byte tree structure. also when wrong value of boolean.
	 */
	public static boolean[] ToBooleanArray(ByteTree b) throws Exception{
		ByteTreeElement element = b.getFirstByteTreeElement();
		if (element == null || !element.isLeaf()) //empty ByteTree or not a leaf
			throw new Exception("the ByteTree " + b + " doesn't match the boolean representation.");
		// convert the leaf's data to a boolean array
		Leaf leaf = (Leaf)element;
		byte[] data = leaf.getDataByteArray();
		boolean[] bool = new boolean[data.length];
		for (int i = 0; i < bool.length; i++){
			// for each byte, if it is 0, then convert it to false, and if it is 1, then convert it to true.
			if (data[i] == 0)
				bool[i] = false;
			else if (data[i] == 1)
				bool[i] = true;
			else
				throw new Exception(data[i] + " is a wrong value and it is doesn't match to a boolean type.");
		}
		// return the boolean array
		return bool;
	}
	
	/**
	 * Converts a field element to a byte tree.
	 * 
	 * @param e the element to convert.
	 * @return the corresponding byte tree to the element e.
	 * @throws Exception 
	 */
	public static ByteTree ToByteTree(FieldElement e) throws Exception{
		Field field = e.getField(); //the field e belongs to
		BigInteger element = e.getElement(); //representation of the element in the field
		BigInteger fieldOrder = field.getOrder(); //the field order
		
		byte[] byteArr = null;
		int numOfBytes = fieldOrder.bitLength();

		if (numOfBytes <= 16) { // if the order can be saved as a short
			byteArr = fromShortToByteArray((short)element.intValue());
		} else if (numOfBytes <= 32) { // if the order can be saved as an int
			byteArr = fromIntToByteArray(element.intValue());
		} else { // otherwise, save it as is
			byte[] fieldElemArr = element.toByteArray();
			byteArr = new byte[fieldOrder.toByteArray().length];
			System.arraycopy(fieldElemArr, 0, byteArr, byteArr.length - fieldElemArr.length, fieldElemArr.length);
		}
		// contain the element's number in a leaf and return it
		Leaf leaf = new Leaf(byteArr, null);
		return new ByteTree(leaf);
	}
	
	/**
	 * Converts a byte tree to a field element.
	 * 
	 * @param b the byte tree to read the field element from.
	 * @param f the field which contains the element.
	 * @return the corresponding field element to the byte tree b in the field f.
	 * @throws Exception invalid byte tree structure.
	 */
	public static FieldElement ToFieldElement(ByteTree b, Field f) throws Exception{
		ByteTreeElement element = b.getFirstByteTreeElement();
		if (element == null || !element.isLeaf()) //empty ByteTree or not a leaf
			throw new Exception("the ByteTree " + b + " doesn't match the FieldElement representation.");

		// convert the leaf's data into a big integer and create a field's element from it.
		Leaf leaf = (Leaf)element;
		BigInteger bigInt = new BigInteger(leaf.getDataByteArray());
		return new FieldElement(bigInt, f);
	}
	
	/**
	 * converts an array of field elements to a byte tree
	 * 
	 * @param e the array of field elements to convert
	 * @return the corresponding byte tree to the array of field elements e
	 * @throws Exception 
	 */
	public static ByteTree ToByteTree(FieldElement[] e) throws Exception{
		// create the containing node
		Node n = new Node(null);
		int length = e.length;
		ByteTree byteArr = null;
		// iterate over each element and create a leaf from it and add it to the containing node
		for (int i = 0; i < length; i++){
			byteArr = ToByteTree(e[i]);
			n.addElement(byteArr.getFirstByteTreeElement());
		}
		// return the containing node
		return new ByteTree(n);
	}
	
	/**
	 * Converts a byte tree to an array of field elements.
	 * 
	 * @param b the byte tree to read the array of field elements from.
	 * @param f the field which contains the elements.
	 * @return the corresponding array of field elements to the byte tree b in the field f.
	 * @throws Exception invalid byte tree structure.
	 */
	public static FieldElement[] ToFieldElementArray(ByteTree b, Field f) throws Exception{
		ByteTreeElement element = b.getFirstByteTreeElement();
		// check for conversion problems
		if (element == null || element instanceof Leaf)
			throw new Exception("the ByteTree " + b + " doesn't match the FieldElement array representation.");
		// the byte tree is a node
		Node rootNode = (Node)element;
		int numberOfChildren = rootNode.getDataLength();
		FieldElement[] elementsArray = new FieldElement[numberOfChildren];
		// iterate through each child and convert it to a field element
		for (int i = 0; i < numberOfChildren; i++){
			elementsArray[i] = ToFieldElement(new ByteTree(rootNode.getChild(i)), f);
		}
		// return the array of elements
		return (elementsArray);
	}
	
	/**
	 * Converts a product field element to a byte tree.
	 * @param e the product field element to convert.
	 * @return the corresponding byte tree to the product field element e.
	 * @throws Exception 
	 */
	public static ByteTree ToByteTree(ProductFieldElement e) throws Exception{
		Node rootNode = new Node(null);
		int numOfElements = e.getNumberOfElements();
		for (int i = 0; i < numOfElements; i++) {
			// iterate through each element and convert it into a byte tree
			Element element = e.getElement(i);
			ByteTree elementByteTree = null;
			if (element instanceof FieldElement) {
				elementByteTree = ToByteTree((FieldElement)element);
			} else {
				 //element is an instance of Product Field Element
				elementByteTree = ToByteTree((ProductFieldElement)element);
			}
			// add the new element's node into the total byte tree
			rootNode.addElement(elementByteTree.getFirstByteTreeElement());
		}
		return (new ByteTree(rootNode));
	}
	
	/**
	 * Converts a byte tree to a product field element.
	 * 
	 * @param b the byte tree to read the product field element from.
	 * @param f the field which contains the elements.
	 * @return the corresponding product field element to the byte tree b in the field f.
	 * @throws Exception invalid byte tree structure.
	 */
	public static ProductFieldElement ToProductFieldElement(ByteTree b, Field f) throws Exception{
		ProductFieldElement productElement = new ProductFieldElement();
		if (b.getFirstByteTreeElement().isLeaf()) {
			throw new Exception("Product Field representation should have its root as a node");
		}
		Node node = (Node)b.getFirstByteTreeElement();
		// run over each byte tree element and convert it to group element
		for (int i = 0; i < node.getDataLength(); i++){
			ByteTreeElement elem = node.getChild(i);
			if (elem.isLeaf()) {
				// if the element is a leaf, than create a field element from it
				productElement.addFieldElement(ToFieldElement(new ByteTree(elem), f));
			} else {
				// if the element is a node, than create a product field element from it
				productElement.addProductFieldElement(ToProductFieldElement(new ByteTree(elem), f));	
			}
		}
		return productElement;
	}
	
	/**
	 * Converts an array of product field elements to a byte tree.
	 * 
	 * @param e the array of product field elements to convert.
	 * @return the corresponding byte tree to the array of product field elements e.
	 * @throws Exception the number of elements isn't equal --> wrong given structure.
	 */
	public static ByteTree ToByteTree(ProductFieldElement[] e) throws Exception{
		// check that all the products have the same size
		if (comparingNumberOfElements(e) == false)
			throw new Exception("the number of elements isn't equal.");

		Node newNode = new Node(null); // the ByteTree to return
		int numberOfElements = e[0].getNumberOfElements();
		// run over all the components in the products
		for (int i = 0; i < numberOfElements; i++) {
			Node n = new Node(null);
			// for each element, save the i-th component
			for (int j = 0; j < e.length; j++) {
				Element element = e[j].getElement(i);
				if (element instanceof FieldElement) {
					// if this is an element
					n.addElement(ToByteTree((FieldElement) element)
							.getFirstByteTreeElement());
				} else {
					// if this is a product
					n.addElement(ToByteTree((ProductFieldElement) element)
							.getFirstByteTreeElement());
				}
			}
			// add the new element
			newNode.addElement(n);
		}
		return new ByteTree(newNode);
	}

	// this function is used to check that 2 product field elements have the same structure 
	private static boolean compareProductFieldElements(ProductFieldElement elem1, ProductFieldElement elem2) {
		int length = elem1.getNumberOfElements();
		if (elem2.getNumberOfElements() != length) {
			return (false);
		}
		
		for (int j = 0; j < length; j++) {
			// check if the product field elements have the same elements inside them
			Element subElem1 = elem1.getElement(j);
			Element subElem2 = elem2.getElement(j);
			// both are field elements
			if ((subElem1 instanceof FieldElement) && (subElem2 instanceof FieldElement)) {
				continue;
			}
			// or both are products of the same structure
			if ((subElem1 instanceof ProductFieldElement) && (subElem2 instanceof ProductFieldElement)) {
				if (compareProductFieldElements((ProductFieldElement)subElem1, (ProductFieldElement)subElem2)) {
					continue;
				}
			}
			return (false);
		}	
		return (true);
	}
	
	// this function is used to check that all product field elements in an array have the same structure
	private static boolean validateArray(ProductFieldElement[] productArray) {
		for (int i = 1; i < productArray.length; i++) { // compare all to 0
			if (!compareProductFieldElements(productArray[0], productArray[i])) {
				return (false);
			}
		}
		
		return (true);
	}
	
	/**
	 * Converts a byte tree to an array of product field elements.
	 * 
	 * @param b the byte tree to read the array of product field elements from.
	 * @param f the field which contains the elements.
	 * @return the corresponding array of product field elements to the byte tree b in the field f.
	 * @throws Exception invalid representation of the product array and when it is inconsistent.
	 * 						also when invalid byte tree structure.
	 */
	public static ProductFieldElement[] ToProductFieldElementArray(ByteTree b, Field f) throws Exception{
		int numOfElementsInProduct = b.getFirstByteTreeElement().getDataLength();
		Node node = (Node)b.getFirstByteTreeElement(); //head node
		Node nodeSon = (Node)node.getChild(0); //one of his sons
		int numOfElementsInProductArray = nodeSon.getDataLength();
		ProductFieldElement[] productProductArr = new ProductFieldElement[numOfElementsInProductArray];
		// first, create all the products in the array
		for (int i = 0; i < numOfElementsInProductArray; i++) {
			productProductArr[i] = new ProductFieldElement();
		}
		// now, iterate over all the nodes and create a 
		for (int i = 0; i < numOfElementsInProduct; i++) {
			nodeSon = (Node)node.getChild(i); // save the current son
			// check that the number of children is the same for all the sons
			if (numOfElementsInProductArray != nodeSon.getDataLength()) {
				throw new Exception("The product array representation is malformed");
			}
			for (int j = 0; j < numOfElementsInProductArray; j++){
				ByteTreeElement elem = nodeSon.getChild(j);
				// create a field element from the byte-tree element
				if (elem.isLeaf()) {
					// this is a field element
					productProductArr[j].addFieldElement(ToFieldElement(new ByteTree(elem), f));
				} else {
					// this is a product field element
					productProductArr[j].addProductFieldElement(ToProductFieldElement(new ByteTree(elem), f));
				}
			}
		}
		
		// now, check that the array has the same type of objects in it
		if (!validateArray(productProductArr)) {
			throw new Exception("The product array is inconsistent");
		}
		
		return productProductArr;
	}
	
	/**
	 * Converts a modular group to a byte tree.
	 * 
	 * @param group the group to convert.
	 * @return the corresponding byte tree to the group named group.
	 * @throws Exception wrong number of bytes to represent the big integers.
	 */
	public static ByteTree ToByteTree(Group group) throws Exception{
		Node n = new Node(null);
		// convert the group to a byte tree by saving its order and containing group's order,
		// and saving the creator and the encoding scheme
		BigInteger p = group.getContainingGroupOrder();
		BigInteger q = group.getOrder();
		BigInteger g = group.getGroupCreator();
		int e = group.getEncodingScheme();
		
		byte[] pArr = representBigInteger(p);
		byte[] qArr = representBigInteger(q);
		byte[] gArr = representBigInteger(g);
		// the encoding scheme should be represented with 4 bytes - not less
		byte[] bArr = fromIntToByteArray(e);
		if (pArr == null || qArr == null || gArr == null)
			return null;

		n.addElement(new Leaf(pArr, null));
		n.addElement(new Leaf(qArr, null));
		n.addElement(new Leaf(gArr, null));
		n.addElement(new Leaf(bArr, null));
		
		return new ByteTree(n);
	}
	
	/**
	 * Converts a byte tree to a modular group.
	 * @param b the byte tree to read the modular group from.
	 * @return the corresponding modular group to the byte tree b.
	 * @throws Exception invalid byte tree structure. also when wrong number of bytes to represent an int.
	 */
	public static ModularGroup ToModularGroup(ByteTree b) throws Exception{
		Node node =(Node)b.getFirstByteTreeElement();
		// check that the are 4 children to the group and that they are all leaves
		if (4 != node.getDataLength()) {
			throw new Exception("The number of children does not match the modular group's tree structure");
		}
		for (int i = 0; i < 4; i++)
			if (!node.getChild(i).isLeaf())
				throw new Exception("The structure of the tree is incorrect for a modular group");
		// convert each leaf into a number
		BigInteger p = new BigInteger(((Leaf)node.getChild(0)).getDataByteArray());
		BigInteger q = new BigInteger(((Leaf)node.getChild(1)).getDataByteArray());
		BigInteger g = new BigInteger(((Leaf)node.getChild(2)).getDataByteArray());
		int e = ToInt(new ByteTree(node.getChild(3).toByteString()));
		// create and return the group
		return new ModularGroup(p, q, g, e);
	}
	
	/**
	 * Converts a group element to a byte tree.
	 * 
	 * @param e the group element to convert.
	 * @return the corresponding byte tree to the group element e.
	 * @throws Exception the element isn't a type of modular group element.
	 */
	public static ByteTree ToByteTree(GroupElement e) throws Exception{
		// only a modular group elements are supported
		if (!(e instanceof ModularGroupElement)) {
			throw new Exception("The element is not of type modular group element");
		}
		ModularGroup group = (ModularGroup)((ModularGroupElement)e).getGroup(); //the group e belongs to
		BigInteger groupElement = ((ModularGroupElement)e).getElement(); //representation of the element in the group
		BigInteger groupOrder = group.getContainingGroupOrder(); //the group order
		
		byte[] byteArr = null;
		int numOfBytes = groupOrder.bitLength();
		
		if (numOfBytes <= 16) {
			byteArr = fromShortToByteArray((short)groupElement.intValue());
		} else if (numOfBytes <= 32) {
			byteArr = fromIntToByteArray(groupElement.intValue());
		} else {
			byte[] groupElemArr = groupElement.toByteArray();
			byteArr = new byte[groupOrder.toByteArray().length];
			System.arraycopy(groupElemArr, 0, byteArr, byteArr.length - groupElemArr.length, groupElemArr.length);
		}
		Leaf leaf = new Leaf(byteArr, null);
		return new ByteTree(leaf);
	}
	
	/**
	 * converts a byte tree to a group element
	 * 
	 * @param b the byte tree to read the group element from
	 * @param group the group which contains the element
	 * @return the corresponding group element to the byte tree b in the group named group
	 * @throws Exception invalid byte tree structure. also when the group isn't a modular one
	 */
	public static GroupElement ToGroupElement(ByteTree b, Group group) throws Exception{
		ByteTreeElement groupElement = b.getFirstByteTreeElement();
		if (groupElement == null || !groupElement.isLeaf()) //empty ByteTree or not a leaf
			throw new Exception("the ByteTree " + b + " doesn't match the FieldElement representation.");
		else {
			Leaf leaf = (Leaf)groupElement;
			BigInteger element = new BigInteger(leaf.getDataByteArray());
			if (group instanceof ModularGroup)
				return new ModularGroupElement(element, (ModularGroup)group);
			else
				throw new Exception("The group is not of type Modular Group");
		}
	}
	
	/**
	 * converts an array of group elements to a byte tree
	 * 
	 * @param e the array of group elements to convert
	 * @return the corresponding byte tree to the array of group elements e
	 * @throws Exception one of the elements (at least) isn't a type of modular group element
	 */
	public static ByteTree ToByteTree(GroupElement[] e) throws Exception{
		Node node = new Node(null);
		int length = e.length;
		ByteTree byteArr = null;
		for (int i = 0; i < length; i++){
			byteArr = ToByteTree(e[i]);
			node.addElement(byteArr.getFirstByteTreeElement());
		}
		return new ByteTree(node);
	}
	
	/**
	 * converts a byte tree to an array of group elements
	 * 
	 * @param b the byte tree to read the array of group elements from
	 * @param group the group which contains the elements
	 * @return the corresponding array of group elements to the byte tree b in the group named group
	 * @throws Exception invalid byte tree structure. also when the group isn't a modular one
	 */
	public static GroupElement[] ToGroupElementArray(ByteTree b, Group group) throws Exception{
		ByteTreeElement groupElement = b.getFirstByteTreeElement();
		if (groupElement == null || groupElement instanceof Leaf)
			throw new Exception("the ByteTree " + b + " doesn't match the FieldElement array representation.");
		Node rootNode = (Node)b.getFirstByteTreeElement();
		int numberOfChildren = rootNode.getDataLength();
		GroupElement[] elementArr = new GroupElement[numberOfChildren];
		for (int i = 0; i < numberOfChildren; i++){
			elementArr[i] = ToGroupElement(new ByteTree(rootNode.getChild(i)), group);
		}
		return elementArr;
	}
	
	/**
	 * converts a product element to a byte tree
	 * 
	 * @param e the product element to convert
	 * @return the corresponding product element to the byte tree b
	 * @throws Exception the element isn't a type of modular group element
	 */
	public static ByteTree ToByteTree(ProductElement e) throws Exception{
		Node rootNode = new Node(null);
		int numOfElements = e.getNumberOfElements();
		for (int i = 0; i < numOfElements; i++) {
			// iterate through each element and convert it into a byte tree
			GroupElement element = e.getElement(i);
			ByteTree elementByteTree = null;
			if (element instanceof ProductElement) {
				 //element is an instance of Product Group Element
				elementByteTree = ToByteTree((ProductElement)element);
			} else {
				elementByteTree = ToByteTree((GroupElement)element);
			}
			// add the new element's node into the total byte tree
			rootNode.addElement(elementByteTree.getFirstByteTreeElement());
		}
		return (new ByteTree(rootNode));
	}
	
	/**
	 * converts a byte tree to a product element
	 * @param b the byte tree to read the product element from
	 * @param group the group which contains the product element
	 * @return the corresponding product element to the byte tree b in the group named group
	 * @throws Exception invalid representation of the product group. also when the group isn't a modular one
	 */
	public static ProductElement ToProductElement(ByteTree b, Group group) throws Exception{
		if (!(group instanceof ProductGroup)) {
			throw new Exception("The group provided is not a product group");
		}
		ProductGroup prodGroup = (ProductGroup)group;
		
		ProductElement productElement = new ProductGroupElement(group);
		if (b.getFirstByteTreeElement().isLeaf()) {
			throw new Exception("Product Group representation should have its root as a node");
		}
		Node node = (Node)b.getFirstByteTreeElement();
		// check that the product size is the same as the product we are about to create
		if (prodGroup.getProductSize() != node.getDataLength()) {
			throw new Exception("The Product in the tree doesn't match the product group provided");
		}
		
		// run over each byte tree element and convert it to group element
		for (int i = 0; i < node.getDataLength(); i++){
			ByteTreeElement elem = node.getChild(i);
			if (elem.isLeaf()) {
				// if the element is a leaf, than create a  group element from it
				productElement.addGroupElement(ToGroupElement(new ByteTree(elem), prodGroup.getGroupInProduct(i)));
			} else {
				// if the element is a node, than create a product group element from it
				productElement.addGroupElement(ToProductElement(new ByteTree(elem), prodGroup.getGroupInProduct(i)));	
			}
		}
		return productElement;
	}
	
	/**
	 * converts an array of product group elements to a byte tree
	 * @param e the array of product group elements to convert
	 * @return the corresponding byte tree to the array of product group elements e
	 * @throws Exception number of elements isn't equal. also when one of the elements (at least)
	 * 						isn't a type of modular group element
	 */
	public static ByteTree ToByteTree(ProductElement[] e) throws Exception{
		if (comparingNumberOfElements(e) == false)
			throw new Exception("number of elements isn't equal.");
		
		Node newNode = new Node(null); //the ByteTree to return
		int numberOfElements = e[0].getNumberOfElements();
		for (int i = 0; i < numberOfElements; i++) {
			Node n = new Node(null);
			for (int j = 0; j < e.length; j++) {
				GroupElement element = e[j].getElement(i);
				if (element instanceof ProductElement) {
					n.addElement(ToByteTree((ProductElement)element).getFirstByteTreeElement());
				} else {
					n.addElement(ToByteTree((GroupElement)element).getFirstByteTreeElement());
				}
			}
			newNode.addElement(n);
		}
		return new ByteTree(newNode);
	}
	
	/**
	 * converts a byte tree to an array of product group elements
	 * @param b the byte tree to read the array of product group elements from
	 * @param group the group which contains the elements
	 * @return the corresponding array of product group elements to the byte tree b in the group named group
	 * @throws Exception invalid representation of modular group. also when the array of product group elements
	 * 						is inconsistent.
	 */
	public static ProductElement[] ToProductElementArray(ByteTree b, Group group) throws Exception {
		if (!(group instanceof ProductGroup)) {
			throw new Exception("The group provided is not a product group");
		}
		ProductGroup prodGroup = (ProductGroup)group;
		
		int numOfElementsInProduct = b.getFirstByteTreeElement().getDataLength();
		// check that the product size is the same as the product we are about to create
		if (prodGroup.getProductSize() != numOfElementsInProduct) {
			throw new Exception("The Product in the tree doesn't match the product group provided");
		}
		
		Node node = (Node)b.getFirstByteTreeElement(); //head node
		Node nodeSon = (Node)node.getChild(0); //one of his sons
		int numOfElementsInProductArray = nodeSon.getDataLength();
		ProductElement[] productProductArr = new ProductElement[numOfElementsInProductArray];
		// first, create all the products in the array
		for (int i = 0; i < numOfElementsInProductArray; i++) {
			productProductArr[i] = new ProductGroupElement(group);
		}
		// now, iterate over all the nodes and create a 
		for (int i = 0; i < numOfElementsInProduct; i++) {
			nodeSon = (Node)node.getChild(i); // save the current son
			// check that the number of children is the same for all the sons
			if (numOfElementsInProductArray != nodeSon.getDataLength()) {
				throw new Exception("The product array representation is malformed");
			}
			for (int j = 0; j < numOfElementsInProductArray; j++){
				ByteTreeElement elem = nodeSon.getChild(j);
				// create a group element from the byte-tree element
				if (elem.isLeaf()) {
					// this is a  group element
					productProductArr[j].addGroupElement(ToGroupElement(new ByteTree(elem), prodGroup.getGroupInProduct(i)));
				} else {
					// this is a product element
					productProductArr[j].addGroupElement(ToProductElement(new ByteTree(elem), prodGroup.getGroupInProduct(i)));
				}
			}
		}
		
		// no need to check that the array has the same type of objects in it, cause the product group is the one who checks it.
		return productProductArr;
	}
	
	/* a helper function - denotes if an int can be represented in a smaller representation */
	private static char shortOrInt(int num){
		short s = (short)num;
		if (s != num) //the number requires int
			return 'i';
		else //the number can be saved as short
			return 's';
	}
	
	/* a helper function - converts an int to byte array */
	private static byte[] fromIntToByteArray(int i){
		ByteBuffer byteBuffer = ByteBuffer.allocate(4);
		IntBuffer intBuffer = byteBuffer.asIntBuffer();
		intBuffer.put(i);
		
		return byteBuffer.array();
	}
	
	/* a helper function - converts a short to byte array */
	private static byte[] fromShortToByteArray(short s){
		ByteBuffer byteBuffer = ByteBuffer.allocate(2);
		ShortBuffer shortBuffer = byteBuffer.asShortBuffer();
		shortBuffer.put(s);
		
		return byteBuffer.array();
	}
	
	/* a helper function - returns true if the number of elements in all product field elements are equal, else - false 
	 */
	private static boolean comparingNumberOfElements(ProductFieldElement[] e){
		// check that each element has the same size as the next one
		for (int i = 0; i < e.length - 1; i++)
			if (e[i].getNumberOfElements() != e[i + 1].getNumberOfElements())
				return (false);

		return true;
	}
	
	/* a helper function - returns true if the number of elements in all product field elements are equal, else - false 
	 */
	private static boolean comparingNumberOfElements(ProductElement[] e){
		// check that each element has the same size as the next one
		for (int i = 0; i < e.length - 1; i++)
			if (e[i].getNumberOfElements() != e[i + 1].getNumberOfElements())
				return (false);

		return true;
	}
	
	// convert a big integer into a byte array for containing it into a byte tree
	private static byte[] representBigInteger(BigInteger num) {
		byte[] byteArr = null;
		int numOfBytes = num.bitLength();
		
		if (numOfBytes <= 16) {
			// if the number can be contained in a short
			byteArr = fromShortToByteArray((short)num.intValue());
			
		} else if (numOfBytes <= 32) {
			// if the number can be contained in an int
			byteArr = fromIntToByteArray(num.intValue());
		} else {
			// otherwise, just keep it as it is
			byte[] groupElemArr = num.toByteArray();
			byteArr = new byte[num.toByteArray().length];
			System.arraycopy(groupElemArr, 0, byteArr, byteArr.length - groupElemArr.length, groupElemArr.length);
		}
		
		return (byteArr);
	}
}